﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ParboiledSharp.lang.roboline.utils;

namespace ParboiledSharp.lang.roboline.context
{
    class Variable
    {
        enum VARTYPE { INTEGER, DOUBLE, STRING, BOOL, FUNCTION };

        private VARTYPE varType;

        private object _value;
        private Dictionary<object, Variable> properties = null;

        public Variable()
        {

        }

        public Variable(object o)
        {
            initVariable(o);
        }

        public void initVariable(object o)
        {
            if (o is Variable)
            {
                Variable v = (Variable)o;
                properties = v.GetProperties();
                varType = v.varType;
                _value = v._value;
            }
            else if (o is int)
            {
                SetInt((int)o);
            }
            else if (o is double)
            {
                SetDouble((double)o);
            }
            else if (o is string)
            {
                SetString((string)o);
            }
            else if (o is bool)
            {
                SetBool((bool)o);
            }
            else
            {
                SetFunction(o);
            }
        }

        public bool IsNull()
        {
            return _value == null;
        }

        public Dictionary<object, Variable> GetProperties()
        {
            if (properties == null)
            {
                properties = new Dictionary<object, Variable>(5);
            }
            return properties;
        }

        public Variable GetProperty(object key)
        {
            return GetProperties()[key];
        }

        public void PutProperty(object key, Variable value)
        {
            GetProperties().Add(key, value);
        }

        private void init()
        {
            properties = new Dictionary<object, Variable>();
        }

        public Variable SetInt(int value)
        {
            varType = VARTYPE.INTEGER;
            _value = value;
            return this;
        }

        public Variable SetBool(bool value)
        {
            varType = VARTYPE.BOOL;
            _value = value;
            return this;
        }

        public Variable SetDouble(double value)
        {
            varType = VARTYPE.DOUBLE;
            _value = value;
            return this;
        }

        public Variable SetString(string value)
        {
            varType = VARTYPE.STRING;
            _value = value;
            return this;
        }

        public Variable SetFunction(object value)
        {
            varType = VARTYPE.FUNCTION;
            _value = value;
            return this;
        }


        public int GetInt()
        {
            switch (varType)
            {
                case VARTYPE.INTEGER: return (int)_value;
                case VARTYPE.STRING: return string.IsNullOrWhiteSpace((string)_value) ? 0 : int.Parse((string)_value);
                case VARTYPE.DOUBLE: return (int)(double)_value;
                case VARTYPE.BOOL: return ((bool)_value) == true ? 1 : 0;
                default: FAILURE(string.Format("Cannot convert variable of type {0} to integer", varType.ToString())); break;
            }
            return 0;
        }

        public double GetDouble()
        {
            switch (varType)
            {
                case VARTYPE.INTEGER: return (double)(int)_value;
                case VARTYPE.STRING: return string.IsNullOrWhiteSpace((string)_value) ? 0.0 : StringUtils.Str2Dbl((string)_value);
                case VARTYPE.DOUBLE: return (double)_value;
                case VARTYPE.BOOL: return ((bool)_value) == true ? 1.0 : 0.0;
                default: FAILURE(string.Format("Cannot convert variable of type {0} to double", varType.ToString())); break;
            }
            return 0;
        }

        public string GetString()
        {
            switch (varType)
            {
                case VARTYPE.INTEGER: return "" + (int)_value;
                case VARTYPE.STRING: return (string)_value;
                case VARTYPE.DOUBLE: return "" + (double)_value;
                case VARTYPE.BOOL: return ((bool)_value) == true ? "true" : "false";
                default: FAILURE(string.Format("Cannot convert variable of type {0} to string", varType.ToString())); break;
            }
            return null;
        }

        public bool GetBool()
        {
            switch (varType)
            {
                case VARTYPE.INTEGER: return (int)_value == 0 ? false : true;
                case VARTYPE.STRING: return (_value != null && (_value.Equals("true"))) ? true : false;
                default: FAILURE(string.Format("Cannot convert variable of type {0} to boolean", varType.ToString())); break;
            }
            return false;
        }

        public object GetFunction()
        {
            switch (varType)
            {
                case VARTYPE.FUNCTION: return _value;
                default: FAILURE(string.Format("Variable of type {0} does not contain executable value", varType.ToString())); break;
            }
            return null;
        }

        public void add(object value)
        {
            Variable tv;
            switch (varType)
            {
                case VARTYPE.INTEGER:
                    tv = Variable.fromObject(value);
                    int rhsIntValue = tv.GetInt();
                    value = GetInt() + rhsIntValue;
                    break;
                case VARTYPE.DOUBLE:
                    tv = Variable.fromObject(value);
                    double rhsDoubleValue = tv.GetDouble();
                    value = GetDouble() + rhsDoubleValue;
                    break;
                case VARTYPE.STRING:
                    tv = Variable.fromObject(value);
                    string rhsStringValue = tv.GetString();
                    value = GetString() + rhsStringValue;
                    break;
                case VARTYPE.BOOL:
                    FAILURE("You cannot add nothing to the boolean variable");
                    break;
                case VARTYPE.FUNCTION:
                    FAILURE("You cannot add nothing to the function variable");
                    break;
            }
        }

        public void subtract(object value)
        {
            Variable tv;
            switch (varType)
            {
                case VARTYPE.INTEGER:
                    tv = Variable.fromObject(value);
                    int rhsIntValue = tv.GetInt();
                    value = GetInt() - rhsIntValue;
                    break;
                case VARTYPE.DOUBLE:
                    tv = Variable.fromObject(value);
                    double rhsDoubleValue = tv.GetDouble();
                    value = GetDouble() - rhsDoubleValue;
                    break;
                case VARTYPE.STRING:
                    tv = Variable.fromObject(value);
                    if (tv.varType == VARTYPE.INTEGER)
                    {
                        rhsIntValue = tv.GetInt();
                        if (rhsIntValue < 0)
                        {
                            rhsIntValue = rhsIntValue * -1;
                            value = StringUtils.removeCharactersFromEnd(GetString(), rhsIntValue);
                        }
                        else
                        {
                            value = StringUtils.removeCharactersFromStart(GetString(), rhsIntValue);
                        }
                        break;
                    }
                    else if (tv.varType == VARTYPE.STRING)
                    {
                        value = StringUtils.removeStringFromEnd(GetString(), tv.GetString());
                        break;
                    }
                    FAILURE(string.Format("You cannot subtract {0} from string", varType.ToString()));
                    break;
                case VARTYPE.BOOL:
                    FAILURE(string.Format("You cannot subtract nothing{0} from the boolean variable", varType.ToString()));
                    break;
                case VARTYPE.FUNCTION:
                    FAILURE(string.Format("You cannot add nothing from the function variable", varType.ToString()));
                    break;
            }
        }

        public void multiple(object value)
        {
            Variable tv;
            switch (varType)
            {
                case VARTYPE.INTEGER:
                    tv = Variable.fromObject(value);
                    int rhsIntValue = tv.GetInt();
                    value = GetInt() * rhsIntValue;
                    break;
                case VARTYPE.DOUBLE:
                    tv = Variable.fromObject(value);
                    double rhsDoubleValue = tv.GetDouble();
                    value = GetDouble() * rhsDoubleValue;
                    break;
                case VARTYPE.STRING:
                    tv = Variable.fromObject(value);
                    if (tv.varType == VARTYPE.INTEGER)
                    {
                        value = StringUtils.repeatString(GetString(), tv.GetInt());
                        break;
                    }
                    FAILURE(string.Format("You cannot multiple {0} on string", varType.ToString()));
                    break;
                case VARTYPE.BOOL:
                    FAILURE(string.Format("You cannot multiple {0} on boolean variable", varType.ToString()));
                    break;
                case VARTYPE.FUNCTION:
                    FAILURE(string.Format("You cannot multiple {0} on function", varType.ToString()));
                    break;
            }
        }

        public void divide(object value)
        {
            Variable tv;
            switch (varType)
            {
                case VARTYPE.INTEGER:
                    tv = Variable.fromObject(value);
                    int rhsIntValue = tv.GetInt();
                    value = GetInt() / rhsIntValue;
                    break;
                case VARTYPE.DOUBLE:
                    tv = Variable.fromObject(value);
                    double rhsDoubleValue = tv.GetDouble();
                    value = GetDouble() / rhsDoubleValue;
                    break;
                case VARTYPE.STRING:
                    tv = Variable.fromObject(value);
                    if (tv.varType == VARTYPE.STRING)
                    {
                        bool b = System.Text.RegularExpressions.Regex.IsMatch(GetString(), tv.GetString());
                        SetBool(b);
                        break;
                    }
                    FAILURE(string.Format("You cannot divide string on {0}", varType.ToString()));
                    break;
                case VARTYPE.BOOL:
                    FAILURE(string.Format("You cannot divide boolean variable on {0}", varType.ToString()));
                    break;
                case VARTYPE.FUNCTION:
                    FAILURE(string.Format("You cannot divide function on {0}", varType.ToString()));
                    break;
            }
        }

        public bool equal(object value)
        {
            Variable tv = Variable.fromObject(value);
            switch (varType)
            {
                case VARTYPE.INTEGER:

                    int rhsIntValue = tv.GetInt();
                    bool result = GetInt() == rhsIntValue;
                    return result;
                case VARTYPE.DOUBLE:
                    double rhsDoubleValue = tv.GetDouble();
                    result = GetDouble() == rhsDoubleValue;
                    return result;
                case VARTYPE.STRING:
                    result = GetString() == tv.GetString();
                    return result;
                case VARTYPE.BOOL:
                    if (tv.varType == VARTYPE.BOOL)
                    {
                        result = GetBool() == tv.GetBool();
                        return result;
                    }
                    FAILURE(string.Format("Boolean variables can be compared only with boolean variables, not with {0}", varType.ToString()));
                    break;
                case VARTYPE.FUNCTION:
                    if (tv.varType == VARTYPE.FUNCTION)
                    {
                        result = GetFunction() == tv.GetFunction();
                        return result;
                    }
                    FAILURE(string.Format("You can compare functions only with functions, not with {0}", varType.ToString()));
                    break;
            }
            return false;
        }

        public bool less(object value)
        {
            return lessMore(value, true);
        }

        public bool more(object value)
        {
            return lessMore(value, false);
        }

        /// <param name="less">if true-compare as LESS, in other case compare as MORE</param>
        /// <returns></returns>
        private bool lessMore(object value, bool less)
        {
            Variable tv = Variable.fromObject(value);
            switch (varType)
            {
                case VARTYPE.INTEGER:

                    int rhsIntValue = tv.GetInt();
                    bool result = less ?
                                    GetInt() < rhsIntValue :
                                    GetInt() > rhsIntValue;
                    return result;
                case VARTYPE.DOUBLE:
                    double rhsDoubleValue = tv.GetDouble();
                    result = less ?
                                    GetDouble() < rhsDoubleValue :
                                    GetDouble() > rhsDoubleValue;
                    return result;
                case VARTYPE.STRING:
                    int compRes = GetString().CompareTo(tv.GetString());
                    if (less)
                    {
                        result = compRes < 0 ? true : false;
                    }
                    else
                    {
                        result = compRes > 0 ? true : false;
                    }

                    return result;
                case VARTYPE.BOOL:
                    int compareResult = CompareUtils.CompareBoolean(GetBool(), tv.GetBool());
                    if (less)
                    {
                        result = compareResult < 0 ? true : false;
                    }
                    else
                    {
                        result = compareResult > 0 ? true : false;
                    }
                    return result;
                case VARTYPE.FUNCTION:
                    FAILURE(string.Format("You cannot compare functions", varType.ToString()));
                    break;
            }
            return false;
        }

        public int compare(object value)
        {
            Variable tv = Variable.fromObject(value);
            int compareresult;
            switch (varType)
            {
                case VARTYPE.INTEGER:
                    int rI = tv.GetInt();
                    int lI = GetInt();
                    if (lI == rI)
                    {
                        compareresult = 0;
                    }
                    else if (lI < rI)
                    {
                        compareresult = -1;
                    }
                    else
                    {
                        compareresult = 1;
                    }

                    return compareresult;
                case VARTYPE.DOUBLE:
                    double rD = tv.GetDouble();
                    double lD = GetDouble();
                    if (lD == rD)
                    {
                        compareresult = 0;
                    }
                    else if (lD < rD)
                    {
                        compareresult = -1;
                    }
                    else
                    {
                        compareresult = 1;
                    }

                    return compareresult;
                case VARTYPE.STRING:
                    compareresult = GetString().CompareTo(tv.GetString());
                    return compareresult;
                case VARTYPE.BOOL:
                    compareresult = CompareUtils.CompareBoolean(GetBool(), tv.GetBool());
                    return compareresult;
                case VARTYPE.FUNCTION:
                    FAILURE(string.Format("You cannot compare functions", varType.ToString()));
                    break;
            }
            return -1;
        }

        public Variable Clone()
        {
            return new Variable(this);
        }

        ///TODO:
        private void FAILURE(string message)
        {
            throw new ParboiledSharp.lang.roboline.exception.ParseException(string.Format("Variable of type {0} does not contain executable value", varType.ToString()), this);
        }

        public static Variable fromObject(object o)
        {
            return o is Variable ? (Variable)o : new Variable(o);
        }
    }
}
